package vg.modules.conveyor;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;

import ru.nsu.fit.iconveyor.conveyor.Conveyor;
import ru.nsu.fit.iconveyor.conveyor.node.Node;
import ru.nsu.fit.iconveyor.conveyor.utils.jar.JarClassLoader;
import ru.nsu.fit.iconveyor.engine.ConveyorExecutionObserver;
import ru.nsu.fit.iconveyor.engine.Engine;
import ru.nsu.fit.iconveyor.engine.EngineStateObserver;

import ru.nsu.fit.iconveyor.engine.smart.SmartEngine;
import vg.modules.conveyor.components.ChangeEdgeParametersDialog;
import vg.modules.conveyor.components.ChangeVertexParametersDialog;
import vg.modules.conveyor.components.ConveyorSettingsDialog;
import vg.services.data_base_manager.data.graph.Edge;
import vg.services.data_base_manager.data.graph.Graph;
import vg.services.data_base_manager.data.graph.Vertex;
import vg.services.data_base_manager.data.header.GraphModelHeader;
import vg.services.graph_view_manager.interfaces.graph_view.AGraphView;
import vg.services.graph_view_manager.interfaces.tab.ATab;
import vg.services.main_manager.MainManager;
import vg.services.plugin_manager.event_and_request.event.AUIEvent;
import vg.services.plugin_manager.event_and_request.event.UIEventChangeTab;
import vg.services.plugin_manager.event_and_request.event.UIEventOpenNewGraph;
import vg.services.plugin_manager.interfaces.IPlugin;
import vg.services.plugin_manager.realization.PluginParameter;
import vg.services.user_interface_manager.interfaces.AUserInterfaceElement;

public class ConveyorPlugin extends AUserInterfaceElement implements IPlugin {
	// Swing components
	private JButton settingsButton;
	private JToolBar toolBar;
	
	private JMenuItem addVertexParametersMenuItem, addEdgeParametersMenuItem;
	
	// Mutex
	private final Object generalMutex = new Object();

	// Main data
	private AGraphView currentGraphView;
	private Map<AGraphView, Engine>  graphView2Engine;   // graph view -> engine
	private Map<Engine, AGraphView>  engine2GraphView;   // engine -> graph view
	private LinkedHashMap<GraphModelHeader, String> graphModel2ExecutorsPath; // model -> executor path
	private LinkedHashMap<GraphModelHeader, Map<String, Class<Node>>> graphModel2ExecutorClasses; // model -> [className, class]

	public ConveyorPlugin() {
		super("Conveyor plugin", null);
	}
	
	@Override
	public void install(PluginParameter pluginParameter) throws Exception {
		currentGraphView = null;
		graphView2Engine = new HashMap<AGraphView, Engine>();
		engine2GraphView = new HashMap<Engine, AGraphView>();
		graphModel2ExecutorsPath = new LinkedHashMap<GraphModelHeader, String>();
		graphModel2ExecutorClasses = new LinkedHashMap<GraphModelHeader, Map<String, Class<Node>>>();
		
		// install importer and exporter
		new ExporterPlugin(this).install(pluginParameter);
        new ImporterPlugin(this).install(pluginParameter);
		
		settingsButton = new JButton(new ImageIcon("./data/resources/textures/conveyor/conveyor_settings.png"));
		settingsButton.setToolTipText("Conveyor settings");
		settingsButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				settings();
			}
		});
		
		addVertexParametersMenuItem = new JMenuItem("Add vertex parameters");
		addVertexParametersMenuItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				Graph graph = currentGraphView.getGraphInformation().getSelectionSubGraph();
				if (!graph.isEmpty() && graph.getVertices() != null && graph.getVertices().size() == 1) {
					Vertex vertex = graph.getVertices().iterator().next();
					int graphModelId = currentGraphView.getLowLevelGraphEditor().getGraphModelId();
					GraphModelHeader gmh = new GraphModelHeader(graphModelId);
					Map<String, Class<Node>> executors = graphModel2ExecutorClasses.get(gmh);
					ChangeVertexParametersDialog.show(executors == null ? null : executors.keySet(), vertex, currentGraphView);
				}
			}
		});
		
		addEdgeParametersMenuItem = new JMenuItem("Add edge parameters");
		addEdgeParametersMenuItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				Graph graph = currentGraphView.getGraphInformation().getSelectionSubGraph();
				if (!graph.isEmpty() && graph.getEdges() != null && graph.getEdges().size() == 1) {
					Edge edge = graph.getEdges().iterator().next();
					ChangeEdgeParametersDialog.show(edge, currentGraphView);
				}
			}
		});
		
		
		settingsButton.setEnabled(false);
		
		toolBar = new JToolBar();
		toolBar.setFloatable(false);
		toolBar.add(settingsButton);

		pluginParameter.userInterface.addInstrument(this);
	}
	
	@Override
	public JComponent getView() {
		return toolBar;
	}
	
	@Override
	public void update(Observable o, Object arg) {
		if(arg instanceof AUIEvent) {
			AUIEvent event = (AUIEvent)arg;
			switch (event.getType()) {
				case DEF_CHANGE_UI_STYLE: {
					if (SwingUtilities.isEventDispatchThread()) {
						SwingUtilities.updateComponentTreeUI(toolBar);
					} else {
						SwingUtilities.invokeLater(new Runnable() {
							public void run() {
								SwingUtilities.updateComponentTreeUI(toolBar);
							}
						});
					}
					break;
				}
				
				case DEF_CHANGE_TAB: {
					UIEventChangeTab bufEvent = (UIEventChangeTab)event;
					final ATab tab = bufEvent.getTab();
					change_view(tab);
					break;
				}
				
				case DEF_OPEN_NEW_GRAPH: {
					UIEventOpenNewGraph bufEvent = (UIEventOpenNewGraph)event;
					addNewModel(bufEvent.getGraphId(), bufEvent.getGraphName(), null);
				}
				
				default: break;
			}
		}
	}
	
//=============================================================================
//-----------------PUBLIC METHODS---------------------------------------------
	public LinkedHashMap<GraphModelHeader, String> getGraphModel2ExecutorsPath() {
		synchronized (generalMutex) {
			return new LinkedHashMap<GraphModelHeader, String>(graphModel2ExecutorsPath);
		}
	}

    public LinkedHashMap<GraphModelHeader, Map<String, Class<Node>>> getGraphModel2ExecutorClasses() {
        synchronized (generalMutex) {
            return new LinkedHashMap(graphModel2ExecutorClasses);
        }
    }

    public void addNewGraphModel2ExecutorsPath(GraphModelHeader modelHeader, String executorsPath) {
		synchronized (generalMutex) {
			graphModel2ExecutorsPath.put(modelHeader, executorsPath);
		}
	}
	
	public void addNewGraphModel2ExecutorClasses(GraphModelHeader modelHeader, Map<String, Class<Node>> executorClasses) {
		synchronized (generalMutex) {
			graphModel2ExecutorClasses.put(modelHeader, executorClasses);
		}		
	}
	
//=============================================================================
//-----------------PRIVATE METHODS---------------------------------------------
	// Call this method from EDT, only
	private void change_view(final ATab tab) {
		// if it's not EDT
		if (!SwingUtilities.isEventDispatchThread()) {
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
					change_view(tab);
				}
			});
			return;
		}
		
		// otherwise (it's EDT)
		synchronized (generalMutex) {
			if (currentGraphView != null) {
				currentGraphView.getGraphObservable().removePopupMenuItem(addVertexParametersMenuItem);
				currentGraphView.getGraphObservable().removePopupMenuItem(addEdgeParametersMenuItem);
			}

			currentGraphView = null;
			if (tab != null) {
				switch (tab.getTabType()) {
					case ATab.DEF_GRAPH_VIEW_TAB_TYPE: {
						currentGraphView = (AGraphView) tab;
						break;
					}
				}
			}

			if (currentGraphView == null) {
				return;
			}

			// add popup menu item
			currentGraphView.getGraphObservable().addPopupMenuItem(addVertexParametersMenuItem);
			currentGraphView.getGraphObservable().addPopupMenuItem(addEdgeParametersMenuItem);

			// update swing components
			if (graphView2Engine.containsKey(currentGraphView)) {
				// get station of pool and sets user components settings
				Engine engine = graphView2Engine.get(currentGraphView);
				switch (engine.getState()) {
					case RUNNING: {
						settingsButton.setEnabled(true);
						break;
					}
					case FAILED:
					case FINISHED:
					case STOPPED: {
						settingsButton.setEnabled(true);
						break;
					}
					default: break;
				}
			} else {
				settingsButton.setEnabled(true);
			}
		}
	}
	
	private void play() {
		synchronized(generalMutex) {
			if (currentGraphView == null) return;

			try {
				GraphModelHeader gmh = new GraphModelHeader(currentGraphView.getLowLevelGraphEditor().getGraphModelId());
				Conveyor conveyor = Converter.visualGraph2conveyor(currentGraphView.getGraphInformation().getGraph(), graphModel2ExecutorClasses.get(gmh));
				final Engine engine = new SmartEngine(1);
				engine.setConveyor(conveyor);
				graphView2Engine.put(currentGraphView, engine);
				engine2GraphView.put(engine, currentGraphView);

				// add listeners
				engine.addConveyorExecutionObserver(new ConveyorExecutionObserver() {
					@Override
					public void onNodeExecuted(String nodeId,
							Collection<String> affectedInputs,
							Collection<String> affectedOutputs) {

					}
				});
				
				engine.addEngineStateObserver(new EngineStateObserver() {
					@Override
					public void onStop() {
						synchronized(generalMutex) {
							AGraphView graphView = engine2GraphView.get(engine);
							if (graphView == currentGraphView) {
								settingsButton.setEnabled(true);
							}
							graphView2Engine.remove(currentGraphView);
							engine2GraphView.remove(engine);
							MainManager.windowMessage.infoMessage("Stop conveyor", "Conveyor message");
						}
					}

					@Override
					public void onStart() {
						AGraphView graphView = engine2GraphView.get(engine);
						if (graphView == currentGraphView) {
							settingsButton.setEnabled(true);
						}
					}

					@Override
					public void onFinish() {
						synchronized(generalMutex) {
							AGraphView graphView = engine2GraphView.get(engine);
							if (graphView == currentGraphView) {
								settingsButton.setEnabled(true);
							}
							graphView2Engine.remove(currentGraphView);
							engine2GraphView.remove(engine);
							MainManager.windowMessage.infoMessage("Finish conveyor", "Conveyor message");
						}
					}

					@Override
					public void onError(Exception cause, boolean stopped) {
						AGraphView graphView = engine2GraphView.get(engine);
						if (graphView == currentGraphView) {
							settingsButton.setEnabled(true);
						}
					}
				});

				engine.start();
			} catch (Exception ex) {
				MainManager.windowMessage.showExceptionMessage(ex);
			}
		}
	}
	
	private void terminate() {
		synchronized(generalMutex) {
			if (currentGraphView == null) {
				return;
			}
			
			Engine engine = graphView2Engine.get(currentGraphView);
			if (engine != null) {
				engine.stop();
			}
		}
	}
	
	private void settings() {
		synchronized (generalMutex) {
			if (currentGraphView == null) return;
			LinkedHashMap<GraphModelHeader, String> old = new LinkedHashMap<GraphModelHeader, String>(graphModel2ExecutorsPath) ;
			ConveyorSettingsDialog.show(graphModel2ExecutorsPath, currentGraphView.getLowLevelGraphEditor().getGraphModelId());
			for (GraphModelHeader buf : old.keySet()) {
				String path1 = old.get(buf);
				String path2 = graphModel2ExecutorsPath.get(buf);
				if ((path2 != null && path1 == null) || (path2 != null && path1 != null && !path2.equals(path1))) {
                    Map<String, Class<Node>> executors = new HashMap<String, Class<Node>>();
                    try {
                        JarClassLoader classLoader = new JarClassLoader(path2);
                        for (String className : classLoader.getClasses()) {
                            Class<?> clazz = classLoader.getClass(className);
                            try {
                                Class<Node> nodeClass = (Class<Node>)clazz;
                                executors.put(className, nodeClass);
                            } catch (Exception ex) {}
                        }
                    } catch (Exception ex) {}

                    if (executors != null) {
                        graphModel2ExecutorClasses.put(buf, executors);
                        MainManager.log.printDebug(executors.toString());
                    }
                }
			}
		}
	}
	
	private void addNewModel(int id, String name, String path) {
		GraphModelHeader modelHeader = new GraphModelHeader(id);
		modelHeader.name = name;
		synchronized (generalMutex) {
			if (!graphModel2ExecutorsPath.containsKey(modelHeader)) {
				graphModel2ExecutorsPath.put(modelHeader, path);
			}
		}
	}
}
